package main

import (
	"io"
	"log"
	"net/http"
	"os"
	"strconv"

	"github.com/gorilla/websocket"
	"github.com/jacobsa/go-serial/serial"
)

// target => client 转发任务
func serial_to_client(client *websocket.Conn, target io.ReadWriteCloser, wsType int) {
	// 退出时关闭 websocket 连接
	defer client.Close()
	// 退出时关闭到目标主机的连接
	defer target.Close()

	// 分配 client 下一帧的写缓冲区
	var buf [1024]byte

	for {
		// 从 target 中读取数据，直到超时
		n, err := target.Read(buf[:])
		// os.ErrDeadlineExceeded 在 go1.14 linux 版本中不存在，需要改为如下方式判断超时
		//if err != nil && !errors.Is(err, os.ErrDeadlineExceeded) {
		if err != nil && !os.IsTimeout(err) {
			log.Println(err)
			return
		}

		// 把读到的数据做为一帧发到 client
		if n > 0 {
			// 取得 client 下一帧的 writer
			w, err := client.NextWriter(wsType)
			if err != nil {
				log.Println(err)
				return
			}
			// 写到 client 下一帧的 writer 缓冲区
			_, err = w.Write(buf[:n])
			if err != nil {
				log.Println(err)
				return
			}
			// 关闭下一帧的 writer，真正地发送数据帧
			err = w.Close()
			if err != nil {
				log.Println(err)
				return
			}
		}
	}
}

// client => target 转发任务
func client_to_serial(client *websocket.Conn, target io.ReadWriteCloser, wsType int) {
	// 退出时关闭 websocket 连接
	defer client.Close()
	// 退出时关闭到目标主机的连接
	defer target.Close()

	for {
		messageType, r, err := client.NextReader()
		if err != nil {
			log.Println(err)
			return
		}
		if messageType == websocket.CloseMessage {
			return
		} else if messageType == websocket.TextMessage || messageType == websocket.BinaryMessage {
			w, err := client.NextWriter(messageType)
			if err != nil {
				return
			}
			// 转发到 target
			if _, err := io.Copy(target, r); err != nil {
				return
			}
			// 结束本帧数据转发
			if err := w.Close(); err != nil {
				return
			}
		}
	}
}

// 本示例提供 WebSocket <=> Serial Proxy 功能
func wsktSerialHandler(w http.ResponseWriter, r *http.Request) {
	var err error

	// 默认串口参数
	options := serial.OpenOptions{
		BaudRate:              115200,
		DataBits:              8,
		StopBits:              1,
		ParityMode:            serial.PARITY_NONE,
		MinimumReadSize:       64,
		InterCharacterTimeout: 100,
	}

	// 从 URL 参数中提取目标串口号和通信参数
	log.Printf("Accept client %s => %s", r.RemoteAddr, r.URL)
	query := r.URL.Query()

	// 提取目标串口号
	if !query.Has("port") {
		log.Println("No port specified !")
		return
	}
	options.PortName = query.Get("port")

	// 波特率
	if query.Has("baudrate") {
		baudrate, err := strconv.ParseUint(query.Get("baudrate"), 10, 32)
		if err != nil {
			log.Println("Invalid baudrate: %v", query.Get("baudrate"))
			return
		}
		options.BaudRate = uint(baudrate)
	}

	if query.Has("databits") {
		databits, err := strconv.Atoi(query.Get("databits"))
		if err != nil {
			log.Println("Invalid databits: %v", query.Get("databits"))
			return
		}
		options.DataBits = uint(databits)
	}

	if query.Has("stopbits") {
		stopbits, err := strconv.Atoi(query.Get("stopbits"))
		if err != nil {
			log.Println("Invalid stopbits: %v", query.Get("stopbits"))
			return
		}
		options.StopBits = uint(stopbits)
	}

	if query.Get("parity") == "odd" {
		options.ParityMode = serial.PARITY_ODD
	} else if query.Get("parity") == "even" {
		options.ParityMode = serial.PARITY_EVEN
	} else if query.Get("parity") != "" {
		log.Println("Invalid parity: %v", query.Get("parity"))
		return
	}

	wsType := websocket.TextMessage
	if query.Get("type") == "binary" {
		wsType = websocket.BinaryMessage
	}

	// Open the port.
	log.Printf("Open port '%s' ...\r\n", options.PortName)
	target, err := serial.Open(options)
	if err != nil {
		log.Println("Filed to open port: %v", err)
		return
	}

	// 升级当前 HTTP 连接为 WebSocket 连接
	log.Println("Upgrade HTTP => WebSocket ...")
	client, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println("Upgrade failed:", err)
		target.Close()
		return
	}

	// 启动 client 连接服务任务
	log.Printf("Proxy: %s <=> (%s = %s)\r\n", client.RemoteAddr(), client.LocalAddr(), options.PortName)
	go client_to_serial(client, target, wsType)
	go serial_to_client(client, target, wsType)
}
